// +----------------------------------------------------------------------+
// |                       	     TpvMarket                                |
// +----------------------------------------------------------------------+
// |                	     www.tpvmarket.com                            |
// +----------------------------------------------------------------------+
// |       	     Copyright (c) 2013 Carlos Mora Molina    			      |
// +----------------------------------------------------------------------+
// | Este programa es software libre. Puede distribuirlo  y/o modificarlo |
// | bajo los términos de la  licencia GNU General Public License  tal  y |
// | como la publica la Fundación de Software Libre, bajo la denominación |
// | GPL, en su versión 2 o posterior (versión inglesa).                  |
// |                                                                      |
// | This program is free software. You can redistribute it and/or modify |
// | it under the terms of the GNU General Public License as published by |
// | the Free Software Foundation; either version 2 or later of the GPL.  |
// +----------------------------------------------------------------------+
// | Autores/Authors:                                                     |
// |          Carlos Mora <ozonosp@gmail.com>                             |
// +----------------------------------------------------------------------+
//
// $Id: Exp $ V3.2

///////////////////////////// CONFIGURACIONES - CONFIGURATIONS  //////////////////////////////////
var config = {
		"popup":{ //Configuration for popup windows
			"enabled":true,	//Be sure that you have loaded jqueryPopups.js on your page
			"errTitle":{"es":"No se puede enviar el formulario debido a los siguientes errores","en":"I cant sent the form", "fr":"Impossible d'envoyer le formulaire, car des erreurs suivantes"}, //Title for popups when show submit general errors
		}, //NOTE:$path contains actual path of this script
		"getlabelnames":false, //If true, when you have a label with "for" attr equal to field "id", label text will appear on error popup
		"inputInsideLabel":true, //True means that field is inside label tags, ¡this option works just if getlabelnames is false!, label text will appear on error popup
		"rulespath":{
			"default":"$path/jprules.inc", //Path for rules file default
			"fr":"$path/jprules.inc", //Path for rules file for french
		},
		"phpremoteurl":"$path/jpremote.php", //Path of the remote script 
		"jsfunctionspath":"$path/jpfunctions.js" //Path for javascript functions file
	}
///////////////////////////// FIN DE LAS CONFIGURACIONES /////////////////////////////////////

var errors = new Object(); //Creamos un objeto vacío para los errores
replacePaths(); //Reemplazamos $path en el fichero de configuraciones por el path

///////////////////////////// INICIO DE LAS FUNCIONES /////////////////////////////////////
jQuery.fn.jpvalidate = function(lang,showformpopup) {
		if(lang == '') lang = 'es';
		getScripts(); //Carga el script cuyo path se encuentra en config.jsfunctionspath
		var validaterules = getRules(); //Carga el fichero de reglas
				
		var form = $(this);
		if(validaterules == false || validaterules == null) return false;
		setElementsAttr(form); //Cambiamos el tamaño de los elementos que tengan un tamaño determinado asignado

		$(form).find('input:text, input:password, textarea').bind('keyup', function() { keyupvalidate($(this),false); });
		$(form).find('input:text, input:password, textarea').bind('focusout', function() { keyupvalidate($(this),true); });
		$(form).find('input:checkbox').bind('change', function() { validatecheckbox($(this)); });
		$(form).find('select').bind('click', function() { validateSelect($(this)); });
		$(form).submit(function(event) { return validateBeforeSubmit($(this),showformpopup); });
		$(form).bind('validate', function(e,showpopup) {  return validateBeforeSubmit($(this),showpopup); });
		
		// Funciones para overlib info sobre el campo
		$(form).find('input, select, textarea').bind('mouseover focusin', function() { showInfo($(this)); });
		$(form).find('input, select, textarea').bind('mouseout focusout', function() { if(config.popup.enabled) jqueryPositForm(false,false); });


		//////////////////// FUNCIONES VALIDACIÓN //////////////////////////////////
		//Validación en keyup
		function keyupvalidate(element,focusout){
			//if(focusout && $(element).hasClass('novalid')) return false; //Si no es valido en el keyup no va a serlo en el focusout
			
			//Sólo entraremos aquí si el campo ya había sido comprobado anteriormente puesto que tiene alguna clase de validación, 
			//gracias a esto la primera vez que accedemos a un campo podremos introducir lo que queramos y no se validará hasta el focusout
			if($(element).hasClass('valid') || $(element).hasClass('novalid') || focusout){ 
				errorMsg = checkLenMaxMin(element); //Comprobamos la longitud y el valor máximo o mínimo
				if(errorMsg != true){ noValid(element,errorMsg,focusout); return false; }
				errorMsg = checkRules(element); //Comprobamos la longitud y el valor máximo o mínimo
				if(errorMsg != true){ noValid(element,errorMsg,focusout); return false; }
				errorMsg = checkFunctions(element); //Comprobamos las funciones para validar el elemento
				if(errorMsg != true){ noValid(element,errorMsg,focusout); return false; }
				errorMsg = checkRemote(element,focusout); //Comprobamos si tiene validación remota
				if(errorMsg != true){ noValid(element,errorMsg,focusout); return false; }
				valid(element); //Si llegamos aquí es que todo es válido
			}
		}
				

		//Comprueba la longitud máxima y mínima así como el valor máximo y mínimo si es numérico
		function checkLenMaxMin(element){
			var rule = validaterules.fields[parsename(element,true)];
			if(rule == null) return true; //Si esta vacío sale
			var elementvalue = $(element).val();
			if($(element).attr('data-jprequired') != undefined) rule.html.required.enabled = $(element).attr('data-jprequired');
			if(elementvalue == '' && rule.html.required.enabled == 'true'){
				if(rule.html.required.msg[lang] == null) return false;
				return rule.html.required.msg[lang];	
			}else if(elementvalue == '') return true; //Si el elemento esta vació y no fallo en la operación aterior es que no es requerido, salimos
			
			if(rule.value.max != '' || rule.value.min != ''){ //Si hay un valor mínimo o máximo se evalua como numérico
				elementvalue = elementvalue.replace(',','.');
				var isnum = /^\d+(\.\d+)?$/.test(elementvalue);
				if(isnum) elementvalue = parseFloat(elementvalue);
				if(elementvalue > parseFloat(rule.value.max) || elementvalue < parseFloat(rule.value.min) || !isnum){
					if(rule.value.msg[lang] == null) return false;
					return rule.value.msg[lang].replace('$1',rule.value.min).replace('$2',rule.value.max); //La salida será el mensaje de error
				}
			}else{
				if(rule.length.min == '' && rule.length.max == '') return true; //Si estan vacío sale
				if(elementvalue.length > parseInt(rule.length.max) || elementvalue.length < parseInt(rule.length.min)){
					if(rule.length.msg[lang] == null) return false;
					return rule.length.msg[lang].replace('$1',rule.length.min).replace('$2',rule.length.max); //La salida será el mensaje de error
				}
			}
			return true;
		}

		//Checkea las reglas para el elemento
		function checkRules(element){
			var fieldrules = validaterules.fields[parsename(element,true)];
			if(fieldrules == null) return true; //Si esta vacío sale
			var fieldrules = fieldrules.rules;
			var elementvalue = $(element).val();
			if(fieldrules == '' || elementvalue == '') return true; //Si no hay reglas o esta vacío sale
			var result = true;
			$.each(fieldrules,function(index,value){ //Para cada regla del elemento
				if(validaterules.rules[value] == undefined) return true;
				var rule = validaterules.rules[value]; //seleccionamos la regla
				var regexp = new RegExp(rule.data);
				if(!regexp.test(elementvalue)){ //Si la regla no es válida
					if(rule.msg[lang] == null) rule.msg[lang] = false;
					result = rule.msg[lang]; //La salida será el mensaje de error;
					return false; //Salimos del bucle pero no de la funcion (jquery es así)
				}
			});
			return result;
		}

		//Ejecuta las funciones que validan el elemento
		function checkFunctions(element){
			var rule = validaterules.fields[parsename(element,true)];
			if(rule == null) return true; //Si esta vacío sale
			var rule = rule.html.jsfunction;
			var elementvalue = $(element).val();
			if(rule == '' || elementvalue == '') return true; //Si esta vacío sale
			var func = window[rule]; //Creamos una funcion que con el nombre que hemos seleccionado del campo
			if(typeof func != 'function') return rule+'() is not defined!';
			var msg = func(element,lang); //Ejecutamos la función
			if(msg != true){
				return msg; //La salida será el mensaje devuelto por la función
			}
			return true;
		}

		//Valida remotamente si se ha de validar
		function checkRemote(element,focusout){
			var rule = validaterules.fields[parsename(element,true)];
			if(rule == null) return true; //Si esta vacío sale
			var rule = rule.html.remote;
			if($(element).attr('data-jpremote') != undefined) rule = $(element).attr('data-jpremote');
			var elementvalue = $(element).val();
			if(rule == '' || rule == 'none' || elementvalue == ''){ return true; //Si esta vacío indica que no hace falta validación alguna
			}else if(rule != 'onkeyup' && !focusout){ return true; //Si se ha marcado algo diferente a keyup evitamos validar cuando el evento sea keyup
			}else{ //Checkeamos remotamente
				var salida = true;
				$.ajax({
					  type: "GET",
					  url: config.phpremoteurl,
					  async: false,
					  data: { "field": parsename(element,false), "value": elementvalue, "lang": lang }
				}).done(function( data ) {
					if(data != 'true') salida = data;
				}).fail(function(){
					salida = "Can't connect with remote validation script.";
				});
				return salida;
			}
		}

		//Valida los checkbox que son requeridos
		function validatecheckbox(element){
			var rule = validaterules.fields[parsename(element,true)];
			if(rule == null) return true; //Si esta vacío sale
			var rule = rule.html.required;
			if(rule == null || rule == '') return true; //Si esta vacío sale
			 if(!$(element).is(':checked') && rule.enabled == 'true'){
			 	 if(rule.msg[lang] == null) rule.msg[lang] = false;
				 noValid(element,rule.msg[lang],true);
				 return false;
			 }
			 valid(element);
		}
		
		//Valida los select que son requeridos
		function validateSelect(element){
			var rule = validaterules.fields[parsename(element,true)];
			if(rule == null) return true; //Si esta vacío sale
			var rule = rule.html.required;
			if(rule == null || rule == '') return true; //Si esta vacío sale
			var elementvalue = $(element).val();
			 if(elementvalue == '' && rule.enabled == 'true'){
			 	 if(rule.msg[lang] == null) rule.msg[lang] = false;
				 noValid(element,rule.msg[lang],true);
				 return false;
			 }
			 valid(element);
		}
		

		//Valida si todos los campos del formulario están ok
		function validateBeforeSubmit(form,showerror){
			clearErrors();
			$('.valid,.novalid').removeClass('valid novalid'); //quitamos todas las clases de validación
			$.each($(form).find('input:text:visible, input:password:visible, textarea:visible'),function(index,field){ //provocamos la validación de cada campo input text
				keyupvalidate(field,true);
			});
			$.each($(form).find('input:checkbox:visible'),function(index,field){ //provocamos la validación de cada campo input text
				validatecheckbox(field);
			});
			$.each($(form).find('select:visible'),function(index,field){ //provocamos la validación de cada campo input text
				validateSelect(field);
			});
			if(!$('.novalid').length) return true;
			if(showerror) showSubmitError();
			return false;
		}
		
		///////////////////////////// FUNCIONES CSS //////////////////////////////////
		//Ajusta el campo como inválido
		function noValid(element,errorMsg,notopen){
			$(element).removeClass('valid');
			$(element).addClass('novalid');
			if(!errorMsg || errorMsg == '') return false; 
			if(config.getlabelnames){
				var label = $("label[for='"+$(element).attr("id")+"']"); //La etiqueta es la que tenga el fro que coincida con el id del elemento
				if(label.length)	errorMsg = '<span class="jpFieldName">'+$(label).text()+'</span> '+errorMsg; //Si hay una etiqueta añadimos el valor al mensaje de error
			}else if(config.inputInsideLabel){
				var label = $(element).parent('label'); //La etiqueta es la que sea el padre del input en cuestion
				if(label.length)	errorMsg = '<span class="jpFieldName">'+$(label).html().split('<')[0]+'</span> '+errorMsg; //Si hay una etiqueta añadimos el valor al mensaje de error
			}
			errors[parsename(element,false)] = errorMsg; //Introducimos el error para el elemento
			if(!notopen) showInfo(element); //Mostramos el popup
		}
		
		//Ajusta el campo como válido
		function valid(element){
			$(element).removeClass('novalid');
			$(element).addClass('valid');
			errors[parsename(element,false)] = ""; //borramos el error del elemento
			if(config.popup.enabled) jqueryPositForm(false,false);
		}

		//Cambia el tamaño de los elementos y les asigna las clases dadas
		function setElementsAttr(form){
			var fields = $(form).find('input:text, select, input:password');
			$.each(fields,function(index,field){ //Para cada input o select
				if(validaterules.fields[parsename(field,true)] != null){
					var width = validaterules.fields[parsename(field,true)].html.hwidth;
					if(width != '') $(field).width(width);
					var classes = validaterules.fields[parsename(field,true)].html.hclass;
					if(classes != '') $(field).addClass(classes);
					var length = validaterules.fields[parsename(field,true)].length.max;
					if(length != '') $(field).attr('maxlength',parseInt(length));
				}
			});
		}

		///////////////////// FUNCIONES ERRORES ///////////////////////////////
		//Muestra información sobre un campo que ha generado un error de validación
		function showInfo(element){
			if($(element).hasClass('novalid') && config.popup.enabled){
				var errorMsg = errors[parsename(element,false)];
				if(errorMsg != null && errorMsg != '') jqueryPositForm(element,errorMsg,''); //popup(errors[parsename(element,false)],element);
			}
		}

		//Muestra una alerta indicando que el formulario no se enviará
		function showSubmitError(){
			var htmlerrors = '<ul>';
			var count = 0;
			$.each(errors,function(index,error){
				if(error == '') return true; //continue
				htmlerrors = htmlerrors + '<li>'+error+'</li>';
				++count;
			});
			htmlerrors = htmlerrors + '</ul>';
			if(!config.popup.enabled || !count) return false;
			jqueryPosit(htmlerrors,config.popup.errTitle[lang]);
		}

		//Borra los errores generados
		function clearErrors(){
			$.each(errors,function(index){
				errors[index] = '';
			});
		}

		////////////////////// OTRAS FUNCIONES /////////////////////////////////
		// Esta función añade las comillas en los nombres de de los campos para evitar problemas con los arrays, devuelve el nombre del elemento
		function parsename(element,defaultifnotexist){
			var name = $(element).attr('name');
			if(name) name = name.replace('[','["').replace(']','"]'); //Si el campo tiene atributo name hace el reemplazo
			
			//Si no existe ningún campo con el nombre indicado se asigna el nombre por defecto
			if(validaterules.fields[name] == null && defaultifnotexist) name = '--default';
			return name;
		}
		
		//Obtiene el fichero de reglas según la configuración dada en config
		function getRules(){
			var salida = new Object();
			//Cargamos el fichero standard si no hay uno definido para el idioma actual
			if(config.rulespath[lang] == undefined){
				var rulesPath = config.rulespath['default'];
			}else{
				var rulesPath = config.rulespath[lang];
			}
			$.ajax({
				  type: "GET",
				  url: rulesPath,
				  async: false,
				  dataType:"json"
			}).done(function(data){
				salida = data;
			}).fail(function(jqXHR, textStatus){ 
				if(textStatus == 'parsererror') alert(rulesPath+" as an incorrect format.\n\nPlease check your rules file."); 
				else alert("There's an error while loading rules file "+rulesPath+"\n\nPlease check url and configuration\n\nError: "+textStatus);
				salida = false;
				});
			return copyDefaultErrMesages(salida);
		}
		
		//Carga los scripts de funciones de las opciones
		function getScripts(){
			$.getScript(config.jsfunctionspath).fail(function(jqxhr, settings, exception) {
			  	alert('Error while loading jsfunctions path:'+config.jsfunctionspath);
			}); 
		}
		
		//Copia los mensajes de error de default en todos los campos que no tengan nada asignado
		function copyDefaultErrMesages(rules){
			$.each(rules.fields,function(field){ //Para cada campo
				if(field == '--default') return true; //Continue si es el campo default
				//Copiamos length.msg
				$.each(rules.fields['--default'].length.msg,function(lang, message){
					if(rules.fields[field].length.msg[lang] == undefined) rules.fields[field].length.msg[lang] = message;
				});
				//Copiamos value.msg
				$.each(rules.fields['--default'].value.msg,function(lang, message){
					if(rules.fields[field].value.msg[lang] == undefined) rules.fields[field].value.msg[lang] = message;
				});
				//Copiamos html.required.msg
				$.each(rules.fields['--default'].html.required.msg,function(lang, message){
					if(rules.fields[field].html.required.msg[lang] == undefined) rules.fields[field].html.required.msg[lang] = message;
				});				
			});
			return rules;
		}
		
};

//Obtiene el path actual del script, debe ejecutarse inmediatamente tras la carga del script o no funcionaría
function replacePaths(){
	var scripts=document.getElementsByTagName('script');
	var path= scripts[scripts.length-1].src.split('?')[0];      // remove any ?query
	var apppath=path.split('/').slice(0, -1).join('/');  // remove last filename part of path and return
	//Reemplazamos $path en el fichero de configuración por el path de la aplicación
	for (var key in config.rulespath) {
		config.rulespath[key] = config.rulespath[key].replace('$path',apppath);
		}
	config.phpremoteurl = config.phpremoteurl.replace('$path',apppath);
	config.jsfunctionspath = config.jsfunctionspath.replace('$path',apppath);
}
